package com.test.A27;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.HttpHeadersReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.util.UrlPathHelper;

public class A27 {

	private static final Logger log = LoggerFactory.getLogger(A27.class);
	
	public static void main(String[] args) throws Exception  {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(WebConfig.class);
        // 1. 测试返回值类型为 ModelAndView

        // 2. 测试返回值类型为 String 时, 把它当做视图名

        // 3. 测试返回值添加了 @ModelAttribute 注解时, 此时需找到默认视图名

        // 4. 测试返回值不加 @ModelAttribute 注解且返回非简单类型时, 此时需找到默认视图名

        // 5. 测试返回值类型为 ResponseEntity 时, 此时不走视图流程

        // 6. 测试返回值类型为 HttpHeaders 时, 此时不走视图流程

        // 7. 测试返回值添加了 @ResponseBody 注解时, 此时不走视图流程
        test7(context);
	}
	
	
	/****
	 * 解析响应类型是user并且返回值类型是@ResponseBody的使用的解析器 RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter()))
	 * @param context
	 * @throws Exception
	 */
    private static void test7(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test7");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                for (String name : response.getHeaderNames()) {
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    /***
     * 解析 返回值是HttpHeaders 使用的解析器HttpHeadersReturnValueHandler
     * @param context
     * @throws Exception
     */
    private static void test6(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test6");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {
                renderView(context, container, webRequest); // 渲染视图
            } else {
                for (String name : response.getHeaderNames()) {//得到响应的head的名字 和值
                    System.out.println(name + "=" + response.getHeader(name));
                }
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }

    /****
     * 测试返回值HttpEntity<User>的  使用的是HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter()))解析器
     * @param context
     * @throws Exception
     */

    private static void test5(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test5");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            if (!container.isRequestHandled()) {  //如果视图已经处理过  说明不需要渲染  这个就不渲染  
                renderView(context, container, webRequest); // 渲染视图
            } else {
            	//直接使用response来得到响应的值
                System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
            }
        }
    }
	
	
	/***
	 * 解析返回值是User测试    使用的解析器是 ServletModelAttributeMethodProcessor(true)  1到4 需要视图渲染 
	 * @param context
	 * @throws Exception
	 */
    private static void test4(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test4");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setRequestURI("/test4");
        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
	
	/****
	 * 测试返回值是user对象 并且有注解是@ModelAttribute    @ModelAttribute 作用把返回的值存储到model中
	 *  使用的是new ServletModelAttributeMethodProcessor(false)  false说明必须有@ModelAttribute注解
	 * 使用@ModelAttribute需要有视图 如果没有的话，需要有@RequestMapping("/test3")，默认会把test3作为视图映射
	 * @param context
	 * @throws Exception
	 */
	 private static void test3(AnnotationConfigApplicationContext context) throws Exception {
	        Method method = Controller.class.getMethod("test3");
	        Controller controller = new Controller();
	        Object returnValue = method.invoke(controller); // 获取返回值

	        HandlerMethod methodHandle = new HandlerMethod(controller, method);

	        ModelAndViewContainer container = new ModelAndViewContainer();
	        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
	        MockHttpServletRequest request = new MockHttpServletRequest();
	        //设置请求路径 并且把请求路径设置到request作用域中
	        request.setRequestURI("/test3");
	        UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);
	        
	        ServletWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
	        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
	            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
	            System.out.println(container.getModel());
	            System.out.println(container.getViewName());
	            renderView(context, container, webRequest); // 渲染视图
	        }
	    }
	
	
	/****
	 * 解析返回值类型是String的测试  使用的解析器是ViewNameMethodReturnValueHandler
	 * @param context
	 * @throws Exception
	 */
	private static void test2(AnnotationConfigApplicationContext context) throws Exception {
        Method method = Controller.class.getMethod("test2");
        Controller controller = new Controller();
        Object returnValue = method.invoke(controller); // 获取返回值

        HandlerMethod methodHandle = new HandlerMethod(controller, method);

        ModelAndViewContainer container = new ModelAndViewContainer();
        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();
        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查是否支持此类型的返回值
            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            renderView(context, container, webRequest); // 渲染视图
        }
    }
	
	/***
	 * 测试返回值类型是ModelAndView类型的 使用的解析器是ModelAndViewMethodReturnValueHandler
	 * @param context
	 * @throws Exception
	 */
	 private static void test1(AnnotationConfigApplicationContext context) throws Exception {
	        Method method = Controller.class.getMethod("test1");
	        Controller controller = new Controller();
	        Object returnValue = method.invoke(controller); //通过反射 获取返回值

	        HandlerMethod methodHandle = new HandlerMethod(controller, method);

	        ModelAndViewContainer container = new ModelAndViewContainer();
	        HandlerMethodReturnValueHandlerComposite composite = getReturnValueHandler();//得到返回值解析器组合
	        
	        ServletWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
	        
	        if (composite.supportsReturnType(methodHandle.getReturnType())) { // 检查解析器组合是否支持此类型的返回值 
	            composite.handleReturnValue(returnValue, methodHandle.getReturnType(), container, webRequest);
	            //处理返回值   一 返回的值   二、方法的返回类型   三 、modelandView的容器   四、请求和响应对象
	            System.out.println(container.getModel());//得到model的内容
	            System.out.println(container.getViewName());//得到view的视图名
	            renderView(context, container, webRequest); // 渲染视图
	        }
	    }
	
	/***
	 * 创建返回值处理器组合
	 * @return
	 */
	 public static HandlerMethodReturnValueHandlerComposite getReturnValueHandler() {
	        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
	        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
	        composite.addHandler(new ViewNameMethodReturnValueHandler());
	        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
	        composite.addHandler(new HttpEntityMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
	        composite.addHandler(new HttpHeadersReturnValueHandler());
	        composite.addHandler(new RequestResponseBodyMethodProcessor(Arrays.asList(new MappingJackson2HttpMessageConverter())));
	        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
	        return composite;
	    }
	
	 /****
	  * 渲染视图
	  * @param context
	  * @param container
	  * @param webRequest
	  * @throws Exception
	  */
	    @SuppressWarnings("all")
	    private static void renderView(AnnotationConfigApplicationContext context, ModelAndViewContainer container,
	                                   ServletWebRequest webRequest) throws Exception {
	        log.info(">>>>>> 渲染视图");
	        FreeMarkerViewResolver resolver = context.getBean(FreeMarkerViewResolver.class);
	        String viewName = container.getViewName() != null ? container.getViewName() : new DefaultRequestToViewNameTranslator().getViewName(webRequest.getRequest());
	        log.info("没有获取到视图名, 采用默认视图名: {}", viewName);
	        // 每次渲染时, 会产生新的视图对象, 它并非被 Spring 所管理, 但确实借助了 Spring 容器来执行初始化
	        View view = resolver.resolveViewName(viewName, Locale.getDefault());
	        view.render(container.getModel(), webRequest.getRequest(), webRequest.getResponse());
	        System.out.println(new String(((MockHttpServletResponse) webRequest.getResponse()).getContentAsByteArray(), StandardCharsets.UTF_8));
	    }
	    
	
    static class Controller {
        private static final Logger log = LoggerFactory.getLogger(Controller.class);

        public ModelAndView test1() {
            log.info("test1()");
            ModelAndView mav = new ModelAndView("view1");
            mav.addObject("name", "张三");
            return mav;
        }

        public String test2() {
            log.info("test2()");
            return "view2";
        }

        @ModelAttribute
        public User test3() {
            log.info("test3()");
            return new User("李四", 20);
        }

        public User test4() {
            log.info("test4()");
            return new User("王五", 30);
        }

        public HttpEntity<User> test5() {
            log.info("test5()");
            return new HttpEntity<>(new User("赵六", 40));
        }

        public HttpHeaders test6() {
            log.info("test6()");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "text/html");
            return headers;
        }

        @ResponseBody
        public User test7() {
            log.info("test7()");
            return new User("钱七", 50);
        }
    }
    /****
             *  必须用 public 修饰, 否则 freemarker 渲染其 name, age 属性时失败
     * @author Administrator
     *
     */
    public static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                   "name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }
}
